const {
  send,
  hexCase,
  sendToTcp,
  crc16Modbus,
  byteOrderType,
  ex16hex,
  bytesToBinary,
} = require('./hex');

let isGetData = false;

const checkSockDataFn = require('./checkSockData');

const { SerialPort } = require('serialport');

function errorSock(data) {
  const funCode = data.slice(2, 4);

  let errMsg = '';

  if (ex16hex(funCode) > 128) {
    // 错误
    const errorCode = data.slice(4, data.length - 4);

    switch (errorCode) {
      case '01':
        errMsg = '非法的功能码服务器不了解功能码';
        break;
      case '02':
        errMsg = '非法的数据地址与请求有关';
        break;
      case '03':
        errMsg = '非法的数据值与请求有关';
        break;
      case '04':
        errMsg = '服务器故障在执行过程中，服务器故障';
        break;
      case '05':
        errMsg =
          '确认服务器接受服务调用，但是需要相对长的时间完成服务。因此，服务器仅返回一个服务调用接收的确认';
        break;
      case '06':
        errMsg = '服务器繁忙服务器不能接受MODBUS请求PDU。客户应用由责任决定是否和何时重发请求';
        break;
      case '0A':
        errMsg = '网关故障网关路经是无效的';
        break;
      case '0B':
        errMsg = '网关故障目标设备没有响应。网关生成这个异常信息';
        break;
      default:
        break;
    }
  }

  return errMsg;
}

function handleModbusDataFn(data = '', arr = [], code) {
  if (data) {
    let newData = data;
    // 一 解析报文： 切割成三组数据
    // 从机地址
    // let address = newData.slice(0, 2);
    // console.log('从机地址', address);
    // 功能码
    // let funCode = newData.slice(2, 4);
    // console.log('功能码', funCode);
    // 用户数据
    let userData = newData.slice(4, newData.length - 4);
    // console.log('用户数据', userData);
    // console.log('arr------', arr);

    const arrData = userData.slice(2, userData.length);

    return handleDataFn(arrData, arr, code);
  }

  // return data;
}

function handleDataFn(data = '', arr = [], code, mod = 'modbus') {
  // window.performance.clearMeasures();
  if (data) {
    if (arr && arr.length) {
      // const num = userData.slice(0,2);
      const modbusData = {};
      const arrData = data;
      const buffer = mod === 'modbus' ? Buffer.alloc(arrData.length / 2) : arrData;
      if (mod === 'modbus') {
        buffer.write(arrData, 'hex');
      }
      // console.log(buffer)
      if (code == 1 || code == 2) {
        let str = arrData + '';
        let leng = str.length / 2;
        let j = '';
        for (let i = 0; i < leng; i++) {
          let str2 = str.slice(2 * i, 2 * i + 2) || 0;
          str2 = parseInt(str2, 16).toString(2).padStart(8, '0');
          str2 = str2.split('').reverse().join('');
          j += str2;
        }

        let a = j + '';
        arr.forEach((item) => {
          // console.log('buffer',key,a[item.keyLeng / 2]);
          if (item.funId) {
            item.value = a[item.keyLeng / 2];

            modbusData[item.funId] = item.value;
          }
        });
      }

      if (code == 3 || code == 4) {
        arr.forEach((item, _key) => {
          const { type = null, decimalPlaces = 0, bitFunIdObj } = item;

          const keyLeng = item.keyLeng;

          switch (type) {
            case 'bit':
            case 'bitOne':
              let newBuf = null;

              if (item.byteOrder && (item.leng === 4 || item.leng === 8)) {
                newBuf = byteOrderType(
                  buffer.slice(keyLeng, keyLeng + item.leng),
                  item.byteOrder,
                  item.leng === '4' ? 'ABCD' : 'ABCDEFGH'
                );
              } else {
                newBuf = buffer.slice(keyLeng, keyLeng + item.leng);
              }

              if (item.byteOrder === 'CDAB' && item.leng === 2) {
                item.value = bytesToBinary(newBuf).split('').reverse();
              } else {
                item.value = bytesToBinary(newBuf).split('');
              }

              break;
            case 'int':
              item.value = buffer.readIntBE(keyLeng, 2);
              break;
            case 'int8':
              item.value = buffer.readInt8(keyLeng);
              break;
            case 'int16':
              item.value = buffer.readInt16BE(keyLeng);
              break;
            case 'uint16':
              item.value = buffer.readUInt16BE(keyLeng);
              break;
            case 'int32':
              if (item.byteOrder) {
                let newBuf = byteOrderType(
                  buffer.slice(keyLeng, keyLeng + item.leng),
                  item.byteOrder,
                  'ABCD'
                );

                item.value = newBuf.readInt32BE(0);
              } else {
                item.value = buffer.readInt32BE(keyLeng);
              }
              break;
            case 'uint32':
              if (item.byteOrder) {
                let newBuf = byteOrderType(
                  buffer.slice(keyLeng, keyLeng + item.leng),
                  item.byteOrder,
                  'ABCD'
                );
                item.value = newBuf.readUInt32BE(0);
              } else {
                item.value = buffer.readUInt32BE(keyLeng);
              }
              // item.value = buffer.readUInt32BE(keyLeng);
              break;
            case 'float':
              if (item.byteOrder) {
                let newBuf = byteOrderType(
                  buffer.slice(keyLeng, keyLeng + item.leng),
                  item.byteOrder,
                  'ABCD'
                );
                item.value = newBuf.readFloatBE(0);
              } else {
                // console.log(keyLeng);
                try {
                  item.value = buffer.readFloatBE(keyLeng);
                } catch (e) {
                  console.log(e);
                  // console.log(keyLeng);
                  // console.log(item);
                }
              }
              break;
            case 'double':
              if (item.byteOrder) {
                let newBuf = byteOrderType(
                  buffer.slice(keyLeng, keyLeng + item.leng),
                  item.byteOrder,
                  'ABCDEFGH'
                );
                item.value = newBuf.readDoubleBE(0);
              } else {
                // console.log(keyLeng);
                try {
                  item.value = buffer.readDoubleBE(keyLeng);
                } catch (e) {
                  console.log(e);
                  // console.log(keyLeng);
                  // console.log(item);
                }
              }
              break;
            case 'text':
              item.value = buffer.toString(item.encoding, keyLeng, keyLeng + item.leng);
              break;
            default:
              break;
          }

          if (
            Object.prototype.toString.call(item.value) !== '[object Array]' &&
            decimalPlaces !== 0
          ) {
            let zero = '';

            for (let i = 0; i < decimalPlaces; i++) {
              zero += '0';
            }

            const num = Number(1 + zero);

            if (!isNaN(num)) {
              item.value = item.value / num;
            }
          }

          // if (item.funId === '1ColorPhaseGet') {
          //   console.log('+++++', item);
          // }
          switch (item.computeType) {
            case 'plus':
              item.value = (item.value + item.computedNum).toFixed(decimalPlaces);
              break;
            case 'reduce':
              item.value = (item.value - item.computedNum).toFixed(decimalPlaces);
              break;
            case 'ride':
              // console.log('------------', item);
              item.value = (Math.round(item.value * item.computedNum * 100) / 100).toFixed(2);
              // console.log('------------', item);
              break;
            case 'except':
              item.value = (item.value / item.computedNum).toFixed(decimalPlaces);
              break;
            default:
              break;
          }

          let MNum = item.decimal === 3 ? 1000 : 100;

          let toFixedNum = !['', null, undefined].includes(item.decimal) ? item.decimal : 2;

          if (Object.prototype.toString.call(item.value) !== '[object Array]') {
            item.value = (Math.round(item.value * MNum) / MNum).toFixed(toFixedNum);
          }

          if (type === 'bit' && bitFunIdObj) {
            for (const i in bitFunIdObj) {
              modbusData[bitFunIdObj[i]] = item.value[i];
            }
          }

          if (item.funId) {
            modbusData[item.funId] = item.value;
          }
        });
      }

      return modbusData;

      // return arr;
    } else {
      return data;
    }
  }

  return data;
}

const dfModbusList = [];

const doList = []; // writeList
class dfModBus {
  closeWin = false;

  options = null;

  isSerialport = false;

  reNum = 5;

  sock = null;

  sOptions = null;

  sockPing = false;

  sockData = '';

  isInit = false;

  num = 0;

  isConnectSerialport = false;

  sendData = '';

  overTime = 5000;

  pending = false;

  modbusId = null;

  name = null;

  isOnLine = false;

  connectType = null;

  writeNum = 0;

  writeTime = null;

  plcId = null;

  connectReadTime = 500;

  constructor(options) {
    this.options = options;

    // const { modbusOptions = null } = options;

    if (options) {
      const {
        connectOverTime = 5000,
        reConnectNum = 5,
        modbusId = null,
        name = null,
        nameEn = null,
        plcId = null,
        connectReadTime = 500,
        protocol,
      } = options;

      this.isInit = true;

      if (modbusId) {
        this.modbusId = modbusId;
      }

      this.isSerialport = protocol === 'Serial Port';

      if (!this.sOptions) {
        this.sOptions = options;
      }

      if (connectOverTime) {
        this.overTime = connectOverTime;
      }

      if (connectReadTime) {
        this.connectReadTime = connectReadTime;
      }

      if (reConnectNum) {
        this.reNum = reConnectNum;
      }

      if (name) {
        this.name = nameEn || name;
      }

      if (plcId) {
        this.plcId = plcId;
      }

      this.initFn(this.options);

      dfModbusList.push(this);
    } else {
      throw new Error('请传入配置参数');
    }
  }

  initFn(options = this.options) {
    const {
      path = null,
      port = null,
      host = null,
      protocol = null,
      baudRate,
      dataBits,
      autoOpen,
      stopBits,
      parity,
    } = options;

    if (!protocol) {
      throw new Error('请传入连接类型');
    }

    this.connectType = protocol;

    if (protocol === 'Serial Port') {
      if (!path) {
        throw new Error('请传入连接地址');
      }

      this.sock = new SerialPort({
        path,
        isSerial: true,
        baudRate,
        dataBits,
        autoOpen: autoOpen === 1,
        stopBits,
        parity,
      });
    } else if (protocol === 'Modbus TCP/IP' || protocol === 'Modbus RTU Over TCP/IP') {
      // begin ---------------------
      const net = require('net');

      if (!host) {
        throw new Error('请传入连接地址');
      }

      if (!port) {
        throw new Error('请传入端口号');
      }

      // console.log(options)

      this.sock = net.connect(
        {
          host,
          port,
        },
        () => {
          console.log(this.name + ' connecting');
        }
      );
    }

    if (this.sock) {
      // 有错误发生调用的事件
      this.sock.on('error', (_e) => {
        // console.log('error', e);
        if (!this.isInit) {
          this.reConnectFn(this.options);
        } else {
          if (this.num <= this.reNum) {
            this.num++;

            setTimeout(() => {
              this.reConnectFn(this.options, true);
            }, 1000);
          }
        }
      });

      //捕获异常，防止崩溃
      this.sock.on('uncaughtException', (err) => {
        //logger.error(err);
        console.log('Err:' + err);

        this.reConnectFn(this.options);
      });

      // socket关闭的事件
      this.sock.on('close', () => {
        console.log(this.name + ' close');
        if (!this.closeWin) {
          this.reConnectFn(this.options);
        }
      });

      // 对方发送了关闭数据包过来的事件
      this.sock.on('end', () => {
        this.reConnectFn(this.options);

        // console.log(this.name + '关闭连接');
      });

      // 当有数据发生的时候，调用;
      this.sock.on('data', (data) => {
        // console.log('当有数据发生的时候，调用data', data);
        // console.log(hexCase(data))
        // console.log(data.toString('hex'));
        // 获取到数据 data
        // sockPing = true;
        this.sockData += hexCase(data);

        this.sockPing = checkSockDataFn(this.sendData, this.sockData, this.connectType);

        // console.log(checkSockDataFn(sendData, sockData));

        // console.log('当有数据发生的时候，sockData', sockData);
        // sock.pause();
      });
    }

    return new Promise((resolve) => {
      // 连接成功调用的事件
      if (this.isSerialport) {
        this.sock.open((error) => {
          if (error) {
            console.log('打开端口' + '错误：' + error);
            resolve(false);

            this.isInit = false;

            if (this.num <= this.reNum) {
              this.num++;

              setTimeout(() => {
                this.reConnectFn(this.options, true);
              }, 1000);
            }
          } else {
            console.log('打开端口成功，正在监听数据中');
            // modbusStore.setIsOnLine(true);

            // modbusStore.removeNotOnlineNameFn(this.name);

            this.isOnLine = true;

            this.isConnectSerialport = true;

            if (readList && readList.length) {
              getDataFn(readList);
            }

            process.send({
              type: 'isOnline',
              success: true,
              name: this.name,
            });

            resolve(true);
          }

          this.isInit = false;
        });
      } else {
        // console.log(this.sock);
        this.sock.on('connect', () => {
          console.log(this.name + ' connect success');

          this.num = 0;

          this.isInit = false;

          // modbusStore.setIsOnLine(true);

          // modbusStore.removeNotOnlineNameFn(this.name);

          this.isOnLine = true;

          if (readList && readList.length) {
            getDataFn(readList);
          }

          process.send({
            type: 'isOnline',
            success: true,
            name: this.name,
          });

          // modbusStore.setSock({sock});

          resolve(true);
        });
      }
    });
  }

  reConnectFn(options = this.options, isConnent = false) {
    if (!this.isInit || isConnent) {
      if (!this.isInit) {
        this.isInit = true;
      }

      this.sock = null;

      this.isConnectSerialport = false;

      if (this.isSerialport) {
        this.sock && this.sock.close();
      }

      this.initFn(options);

      process.send({
        type: 'isOnline',
        success: false,
        name: this.name,
      });

      // modbusStore.setIsOnLine(false);

      // modbusStore.setNotOnlineNameFn(this.name);

      this.isOnLine = false;
    }
  }

  async commandFn(code, id, addr, leng, isClick = false) {
    if (this.sock) {
      // if (pending) {
      //   return new Promise(function (resolve) {
      //     waitCommandList.push({
      //       code,
      //       id,
      //       addr,
      //       leng,
      //       isClick,
      //       resolve
      //     });
      //   });
      // }

      let newData = send(code, id, addr, leng).toUpperCase();

      // if(isClick){
      //   console.log(11);

      //   console.log(code);

      //   console.log(id);

      //   console.log(addr);

      //   console.log(leng);

      //   console.log(newData)

      //   console.log(22)
      // }

      if (this.connectType === 'Modbus TCP/IP') {
        newData = sendToTcp(newData);
      }

      this.sendData = newData;

      this.sockPing = false;

      this.sockData = '';

      this.pending = true;

      // console.log(newData);
      if (isClick) {
        console.log('发送数据');

        if (this.writeTime) {
          clearInterval(this.writeTime);

          this.writeTime = null;
        }

        // console.log(`modbusId:${this.modbusId}读取间隔`, this.writeNum);

        this.writeNum = 0;

        this.writeTime = setInterval(() => {
          this.writeNum++;
        }, 1);
      }

      // console.log('发送数据');

      // if (this.modbusId === '43') {
      //   if (this.writeTime) {
      //     clearInterval(this.writeTime);

      //     this.writeTime = null;
      //   }

      //   console.log(`modbusId:${this.modbusId}读取间隔`, this.writeNum);

      //   this.writeNum = 0;

      //   this.writeTime = setInterval(() => {
      //     this.writeNum++;
      //   }, 1);
      // } else {
      //   // console.log(`modbusId:${this.modbusId}`, this.writeNum);
      // }

      this.sock.write(Buffer.from(newData, 'hex'), (e) => {
        if (e) {
          console.log('写入失败');

          console.log(e);

          this.pending = false;

          process.send({
            type: isClick ? 'write' : 'read',
            success: false,
            name: this.name,
            err: e,
            msg: `${isClick ? '写入' : '读取'}失败`,
          });
        }
      });

      // sock.resume();
      // console.log(data)
      return new Promise((resolve) => {
        let num = 0;
        // console.log(sendData);

        if (isClick) {
          console.log('写入报文');
          console.log(this.sendData);
        }

        let sockPingCommand = setInterval(async () => {
          num += 10;

          // if (isClick) {
          //   console.log(this.sockData);

          //   console.log(this.overTime);

          //   console.log(num);
          // }

          if (this.sockPing || num >= this.overTime) {
            clearInterval(sockPingCommand);

            this.pending = false;

            if (isClick) {
              // console.log(this.sockData);

              console.log(`name:${this.name}写入耗时`, this.writeNum);

              if (this.writeTime) {
                clearInterval(this.writeTime);

                this.writeTime = null;
              }
            }

            // console.log(sockPing);

            // console.log(sockData);

            let data = false;

            if (this.sockPing) {
              data = this.sockData;

              // console.log(this.connectType);

              if (this.connectType === 'Modbus TCP/IP') {
                data = data.slice(12);

                data = `${data}${crc16Modbus(data)}`;
              }
              // if (isClick) {
              //   console.log('data', data);
              // }
            }

            if (!this.sockPing) {
              console.log(this.name + ' sendData error');

              console.log(this.sendData);

              console.log(this.sockData);

              console.log(code);

              console.log(leng);

              console.log(addr);

              process.send({
                type: isClick ? 'write' : 'read',
                success: false,
                isTimeOut: true,
                name: this.name,
                err: this.sendData,
                msg: this.sockData,
              });

              this.sendData = '';

              this.sockData = '';
            }

            // if (this.modbusId === '43') {
            //   if (this.writeTime) {
            //     clearInterval(this.writeTime);

            //     this.writeTime = null;
            //   }

            //   console.log(`modbusId:${this.modbusId}读取耗时`, this.writeNum);

            //   this.writeNum = 0;

            //   this.writeTime = setInterval(() => {
            //     this.writeNum++;
            //   }, 1);
            // }

            resolve(this.sockPing ? data : false);

            // if (waitCommandList && waitCommandList.length) {
            //   const {
            //     code = null,
            //     id = null,
            //     addr = null,
            //     leng = null,
            //     isClick = false,
            //   } = waitCommandList[0];

            //   waitCommandList.shift();

            //   command(code, id, addr, leng, isClick);
            // }
          }
        }, 10);
      });
    } else {
      return false;
    }
  }

  closeSockFn() {
    if (this.sock) {
      this.sock.close();

      this.closeWin = true;
    }
  }
}

let modbus = null;

// let intervalTime = null;

let intervalNum = 0;

let readList = [];

let getDataTime = null;

const getDataFn = async (list,isWrite=false,writeId=null) => {
  if(isGetData){
    return;
  }

  isGetData = true;

  const {
    isOnLine = false,
    sock = null,
    commandFn = null,
    name = null
  } = modbus || {};

  if (!isWrite){
    if (intervalNum > list.length - 1) {
      intervalNum = 0;
    }
  }
  // console.log(isOnLine);

  // console.log(modbus.isOnLine);

  // console.log(modbus);

  // console.log(isOnLine);

  if (sock && isOnLine && commandFn && list[isWrite?0:intervalNum]) {
    const {
      code,
      id,
      addr,
      list: arr,
      value = null,
      valueLeng = null,
      readArr=null
    } = list[isWrite ? 0 : intervalNum];

    let {leng} = list[isWrite ? 0 : intervalNum];

    if (isWrite && value !== null) {
      switch (code) {
        case 5:
          leng = value;
          break;
        case 6:
        case 15:
          leng = list[isWrite ? 0 : intervalNum];
          break;
        case 10:
          if (valueLeng) {
            leng = valueLeng;
          } else {
            leng = {
              addrNum: leng / 2,
              regByte: leng,
              valueArr: [
                {
                  ...list[isWrite ? 0 : intervalNum],
                  value: value
                }
              ]
            };
          }
          break;
        default:
          break;
      }
    }

    let res = await modbus.commandFn(code, id, addr, leng, isWrite);

    if (res !== false) {
      const errMsg = errorSock(res);

      if (errMsg) {
        // todo
        process.send({
          type: isWrite ? 'write' : 'read',
          success: false,
          name,
          err: `${isClick ? '写入' : '读取'}失败`,
          msg: errMsg
        });
      } else {
        // console.log(res);

        res = handleModbusDataFn(res, readArr?readArr:arr, code);

        if (typeof res === 'object' && !isWrite) {
          process.send({
            type: 'read',
            success: true,
            name,
            data:res
          });
          // process.send(res);

          // console.log(11)
        }

        if(isWrite){
          process.send({
            type: 'write',
            success: true,
            name,
            id: writeId,
            data: readArr ? res : null
          });
        }
      }
    } else {
      console.log('error');

      process.send({
        type: isWrite ? 'write' : 'read',
        success: false,
        name,
        err: `${isWrite ? '写入' : '读取'}失败`,
        msg: 'error',
        id: writeId
      });
    }

    if (!isWrite) {
      if (intervalNum >= list.length - 1) {
        intervalNum = 0;
      } else {
        intervalNum++;
      }
    }
  }

  // console.log(doList.length)

  isGetData = false;

  if(doList&&doList.length){
    const {writeObj=null,id = null} = doList[0];

    if (writeObj) {
      getDataFn([writeObj], true, id);
    }

    doList.shift();
  }else{
    if(readList && readList.length){
      if(getDataTime){
        clearTimeout(getDataTime);

        getDataTime = null;
      }

      getDataTime = setTimeout(() => {
        getDataFn(readList);
      }, modbus?.connectReadTime||500);
      // getDataFn(readList);
    }
  }
  // setTimeout(() => {
  //   getDataFn(list);
  // }, 1000);
};

const initFn = async(data)=>{
  if(data){
    const { options } = data;

    if (options) {
      modbus = new dfModBus({
        ...options,
        host: options.ip?options.ip:options.host
      });

      // console.log(options);
      // console.log(addressList);

      readList = options.addressList || [];
    }
  }
}

const deleteFn = (data)=>{
  console.log('关闭')

  // if(intervalTime){
  //   clearInterval(intervalTime);

  //   intervalTime = null;
  // }

  if (modbus && modbus.closeSockFn) {
    modbus.closeSockFn();
  }

  modbus = null;

  process.exit();
}

const writeFn = async obj => {

  const { isOnLine = false, sock = null, name=null } = modbus || {};

  if (isOnLine && sock && obj) {

    const { writeObj = null, id = null } = obj;

    if (readList && !readList.length && doList && !doList.length) {
      if (writeObj) {
        console.log(isGetData);

        if (!isGetData) {
          getDataFn([writeObj], true, id);
        }else{
          doList.push(obj);
        }
      }
    } else {
      console.log(isGetData);
      console.log(obj)
      if(!isGetData){
        getDataFn([writeObj], true, id);
      }else{
        doList.push(obj);
      }
    }

    // console.log(121212)
  }else{
    process.send({
      type: 'write',
      success: false,
      name: name,
      err: `isOnLine:${isOnLine}`,
      msg: `写入失败`
    });
  }
};

const connectFn = () => {
  const {
    isOnLine = false,
    num = 0,
    reNum = 0,
    reConnectFn = null,
    sOptions = null,
    name = null
  } = modbus;

  if (!isOnLine && reConnectFn && sOptions && num > reNum) {
    modbus.num = 0;

    console.log(name + '重连');

    setTimeout(() => {
      modbus.reConnectFn(sOptions, true);
    }, 1000);
  }
};

process.on('message', obj => {
  if (obj) {
    const { type = null, data = null } = obj;

    switch (type) {
      case 'init':
        initFn(obj);
        break;

      case 'write':
        writeFn(data);
        break;

      case 'connect':
        connectFn();
        break;

      case 'delete':
        deleteFn(obj);
        break;

      default:
        break;
    }
  }
});
